<template>
  <div>
    <el-button type="primary" @click="openDialog">open</el-button>
    <div id="mountNodes"></div>
    <Antv ref="AntvRef" />
  </div>
</template>

<script setup lang="ts">
  import { nextTick, onMounted, ref } from "vue"
  import G6 from '@antv/g6'
  import Antv from "@/components/Antv.vue"

  onMounted(async () => {
    await getData()
    await main()
  })

  let remoteData
  const remoteData2 = {
    nodes: [
      { id: 'node1', x: 100, y: 100, label: '菱形1', type: 'diamond' }, // 最简单的
      { id: 'node2', x: 50, y: 150, label: '菱形2', type: 'diamond', size: [50, 100] }, // 添加宽高
      { id: 'node3', x: 100, y: 150, label: '菱形3' ,color: 'red', type: 'diamond' }, // 添加颜色
      { id: 'node4', x: 150, y: 150, label: '菱形4', type: 'diamond' }, // 附加文本
    ],
    edges: [
      { label: "e1-1", source: "node1", target: "node2", weight: 1 },
      { label: "e2-1", source: "node1", target: "node3", weight: 1 },
      { label: "e3-1", source: "node1", target: "node4", weight: 1 },
    ]
  }
  const remoteData3 = {
    id: "1",
    type: 'dd',
    label: '111',
    children: [
      {
        id: "1-1",
        type: 'dd',
        label: '222',
        children: [
          {
            id: "1-1-1",
            label: '菱形1'
          },
          {
            id: "1-1-2"
          }
        ]
      },
      {
        id: "1-2",
      },
      {
        id: "1-3",
      }
    ]
  }
  async function getData() {
    const response = await fetch(
      'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json',
    )
    remoteData = await response.json()

    remoteData.nodes.forEach((node) => {
      if (!node.style) {
        node.style = {}
      }
      switch (
        node.class // 根据节点数据中的 class 属性配置图形
        ) {
        case 'c0': {
          node.type = 'circle' // class = 'c0' 时节点图形为 circle
          break
        }
        case 'c1': {
          node.type = 'rect' // class = 'c1' 时节点图形为 rect
          node.size = [35, 20] // class = 'c1' 时节点大小
          break
        }
        case 'c2': {
          node.type = 'ellipse' // class = 'c2' 时节点图形为 ellipse
          node.size = [120, 120] // class = 'c2' 时节点大小
          break
        }
      }
    })
    remoteData.edges.forEach((edge) => {
      if (!edge.style) {
        edge.style = {}
      }
      edge.style.lineWidth = edge.weight
      edge.style.opacity = 0.6
      edge.style.stroke = 'grey'
    })
  }

  let graph
  let minimap
  async function main() {
    minimap = new G6.Minimap({
      size: [200, 200],
      className: 'minimap',
      type: 'delegate',
    })
    graph = new G6.TreeGraph({
      container: 'mountNodes', // String | HTMLElement，必须，在 Step 1 中创建的容器 id 或容器本身
      width: 1000, // Number，必须，图的宽度
      height: 500, // Number，必须，图的高度
      fitView: true,
      fitViewPadding: 200,
      minZoom: 0.1,
      maxZoom: 99,
      renderer: "canvas",
      // 点在默认状态下的样式配置（style）和其他配置
      defaultNode: {
        size: 30, // 节点大小
        style: {
          fill: 'steelblue', // 节点填充色
          stroke: '#666', // 节点描边色
          lineWidth: 1, // 节点描边粗细
        },
        // 节点上的标签文本配置
        labelCfg: {
          // 节点上的标签文本样式配置
          style: {
            fill: '#000', // 节点标签文字颜色
          },
        },
      },
      // 边在默认状态下的样式配置（style）和其他配置
      defaultEdge: {
        type: "arrow-running",
        style: {
          lineWidth: 2,
          stroke: "#bae7ff"
        },
        // 边上的标签文本配置
        labelCfg: {
          autoRotate: true, // 边上的标签文本根据边的方向旋转
        },
      },
      nodeStateStyles: {
        // 鼠标 hover 上节点，即 hover 状态为 true 时的样式
        hover: {
          fill: 'lightsteelblue',
        },
        // 鼠标点击节点，即 click 状态为 true 时的样式
        click: {
          stroke: '#000',
          lineWidth: 3,
        },
        closest: {
          fill: '#f00',
        },
      },
      // 边不同状态下的样式集合
      edgeStateStyles: {
        // 鼠标点击边，即 click 状态为 true 时的样式
        click: {
          stroke: 'steelblue',
        },
      },
      // 将 minimap 实例配置到图上
      plugins: [minimap],
      modes: {
        // 允许拖拽画布、放缩画布、拖拽节点
        default: ['drag-canvas', 'zoom-canvas', 'drag-node',           
          {
            type: 'tooltip', // 提示框
            formatText(model) {
              // 提示框文本内容
              const text = 'id: ' + model.id
              return text
            }
          },
          {
            type: 'edge-tooltip', // 边提示框
            formatText(model) {
              // 边提示框文本内容
              const text =
                'source: ' +
                model.source +
                '<br/> target: ' +
                model.target
              return text
            },
          },
        ]
      },
      layout: {
        type: 'compactBox', // 布局类型
        direction: 'TB', // 自左至右布局，可选的有 H / V / LR / RL / TB / BT
        nodeSep: 200, // 节点之间间距
        rankSep: 200, // 每个层级之间的间距
        excludeInvisibles: true, // 布局计算是否排除掉隐藏的节点，v4.8.8 起支持
      },
    })
    customNode()
    customEageAnimate()
    customEvent()
    graph.data(remoteData3) // 加载远程数据
    graph.render() // 渲染
  }
  
  function customNode() {
    G6.registerNode('dd', {
      draw(cfg, group) {
        // 如果 cfg 中定义了 style 需要同这里的属性进行融合
        const keyShape = group.addShape('path', {
          attrs: {
            path: this.getPath(cfg), // 根据配置获取路径
            stroke: "#000", // 颜色应用到描边上，如果应用到填充，则使用 fill: cfg.color
          },
          // 在 G6 3.3 及之后的版本中，必须指定 name，可以是任意字符串，但需要在同一个自定义元素类型中保持唯一性
          name: 'path-shape',
          // 设置 draggable 以允许响应鼠标的图拽事件
          draggable: true,
        })
        if (cfg.label) {
          // 如果有文本
          // 如果需要复杂的文本配置项，可以通过 labeCfg 传入
          // const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
          // style.text = cfg.label;
          const label = group.addShape('text', {
            // attrs: style
            attrs: {
              x: 0, // 居中
              y: 0,
              textAlign: 'center',
              textBaseline: 'middle',
              text: cfg.label,
              fill: '#666',
            },
            // 在 G6 3.3 及之后的版本中，必须指定 name，可以是任意字符串，但需要在同一个自定义元素类型中保持唯一性
            name: 'text-shape',
            // 设置 draggable 以允许响应鼠标的图拽事件
            draggable: true,
          })
        }
        return keyShape
      },
      // 返回菱形的路径
      getPath(cfg) {
        const width = 30
        const height = 30
        //  / 1 \
        // 4     2
        //  \ 3 /
        const path = [
          ['M', 0, 0 - height / 2], // 上部顶点
          ['L', width / 2, 0], // 右侧顶点
          ['L', 0, height / 2], // 下部顶点
          ['L', -width / 2, 0], // 左侧顶点
          ['Z'], // 封闭
        ]
        return path
      },
    })
  }
  const { getLabelPosition, transform } = G6.Util
  function customEageAnimate() {
    G6.registerEdge(
      "arrow-running",
      {
        afterDraw(cfg, group) {
          // 得到组中的第一个形状，这是边的路径=
          console.log(group)
          const shape = group!.get("children")[0]

          const arrow = group!.addShape("marker", {
            attrs: {
              x: 16,
              y: 0,
              r: 8,
              lineWidth: 2,
              stroke: "#3370ff",
              fill: "#fff",
              symbol: (x, y, r) => {
                return [
                  ["M", x - 6, y - 4],
                  ["L", x - 2, y],
                  ["L", x - 6, y + 4]
                ]
              }
            },
            name: 'path-marker'
          })

          // 箭头动画
          arrow.animate(
            (ratio) => {
              // 每个帧中的操作。比率的范围从0到1，表示动画的进度。返回修改后的配置
              // 根据比例得到边缘的位置
              const tmpPoint = shape.getPoint(ratio)
              const pos = getLabelPosition(shape, ratio)
              let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
              matrix = transform(matrix, [
                ["t", -tmpPoint.x, -tmpPoint.y],
                ["r", pos.angle],
                ["t", tmpPoint.x, tmpPoint.y]
              ])

              // 在此处返回修改后的配置，在此处返回x和y
              return {
                x: tmpPoint.x,
                y: tmpPoint.y,
                matrix
              }
            },
            {
              repeat: true, // 是否重复执行动画
              duration: 3000
            }
          )
        }
      },
      "cubic" // 继承内置边“cubic”
    )
  }
  let minDisNode
  function customEvent() {
    // 鼠标进入节点
    graph.on('node:mouseenter', (e) => {
      const nodeItem = e.item // 获取鼠标进入的节点元素对象
      graph.setItemState(nodeItem, 'hover', true) // 设置当前节点的 hover 状态为 true
    })

    // 鼠标离开节点
    graph.on('node:mouseleave', (e) => {
      const nodeItem = e.item // 获取鼠标离开的节点元素对象
      graph.setItemState(nodeItem, 'hover', false) // 设置当前节点的 hover 状态为 false
    })

    // 点击节点
    graph.on('node:click', (e) => {
      // 先将所有当前是 click 状态的节点置为非 click 状态
      const clickNodes = graph.findAllByState('node', 'click')
      clickNodes.forEach((cn) => {
        graph.setItemState(cn, 'click', false)
      })
      const nodeItem = e.item // 获取被点击的节点元素对象
      graph.setItemState(nodeItem, 'click', true) // 设置当前节点的 click 状态为 true
    })

    // 点击边
    graph.on('edge:click', (e) => {
      // 先将所有当前是 click 状态的边置为非 click 状态
      const clickEdges = graph.findAllByState('edge', 'click')
      clickEdges.forEach((ce) => {
        graph.setItemState(ce, 'click', false)
      })
      const edgeItem = e.item // 获取被点击的边元素对象
      graph.setItemState(edgeItem, 'click', true) // 设置当前边的 click 状态为 true
    })

    graph.on('node:drop', (e) => {
      console.log('node:drop', e)
    })

    graph.on('node:dragstart', (e) => {
      console.log('node:dragstart', e)
      minDisNode = undefined
    })

    graph.on('node:drag', (e) => {
      minDisNode = undefined
      const item = e.item
      const model = item.getModel()
      const nodes = graph.getNodes()
      let minDis = Infinity
      nodes.forEach((item) => {
        graph.setItemState(item, 'closest', false)
        const model2 = item.getModel()
        if (model2.id === model.id) return
        const dis = (model2.x - e.x) * (model2.x - e.x) + (model2.y - e.y) * (model2.y - e.y)
        if (dis < minDis) {
          minDis = dis
          minDisNode = item
        }
      })
      // console.log('minDis', minDis, minDisNode)
      if (minDis < 2000) graph.setItemState(minDisNode, 'closest', true)
      else minDisNode = undefined
    })

    graph.on('node:dragend', (e) => {
      if (!minDisNode) {
        // descriptionDiv.innerHTML = 'Failed. No node close to the dragged node.'
        return
      }
      const dragItem = e.item
      const dragId = dragItem.getID()
      const dragData = graph.findDataById(dragId)
      // if the minDisNode is a descent of the dragged node, return
      let isDescent = false
      const minDisNodeId = minDisNode.getID()

      G6.Util.traverseTree(dragData, (d) => {
        if (d.id === minDisNodeId) isDescent = true
      })
      if (isDescent) {
        // descriptionDiv.innerHTML = 'Failed. The target node is a descendant of the dragged node.'
        return
      }
      graph.removeChild(dragId)

      setTimeout(() => {
        const newParentData = graph.findDataById(minDisNodeId)
        let newChildren = newParentData.children
        if (newChildren) newChildren.push(dragData)
        else newChildren = [dragData]
        graph.updateChildren(newChildren, minDisNodeId)
      }, 600)
    })
  }

  const AntvRef = ref()
  const openDialog = () => {
    AntvRef.value.openDialog()
  }

</script>

<style scoped lang="scss">
  :deep(.g6-tooltip) {
    border: 1px solid #e2e2e2;
    border-radius: 4px;
    font-size: 12px;
    color: #545454;
    background-color: rgba(255, 255, 255, 0.9);
    padding: 10px 8px;
    box-shadow: rgb(174, 174, 174) 0px 0px 10px;
  }
  .a {
    width: 100px;
    height: 100px;
  }
</style>